﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace Phoenix.Xna.Framework.Input.Mice
{
    /// <summary>
    /// Tracks the state of a combination of mouse buttons, allowing for complex mapping of user input to engine action.
    /// </summary>
    public class MouseButtonMap : InputMap
    {
        /// <summary>
        /// Raised when the mapped button combination is pressed.
        /// </summary>
        public event EventHandler<InputMapEventArgs<MouseButtonMap>> Pressed;

        /// <summary>
        /// Raised when the mapped button combination is released.
        /// </summary>
        public event EventHandler<InputMapEventArgs<MouseButtonMap>> Released;

        /// <summary>
        /// Raised when the mapped button combination is held.
        /// </summary>
        public event EventHandler<InputMapEventArgs<MouseButtonMap>> Held;

        /// <summary>
        /// A value indicating the default hold threshold.
        /// </summary>
        public static int DefaultHoldThreshold = 100;

        /// <summary>
        /// A value indicating the default repeat rate.
        /// </summary>
        public static int DefaultRepeatRate = 10;

        private int _lastRepeatTimeSpan;    // The timespan since the ButtonMap was last reported as repeated.
        private int _millisecondsHeld;      // The timespan since the ButtonMap was last reported as held.

        /// <summary>
        /// Gets the list of mapped buttons.
        /// </summary>
        public List<Buttons> MappedButtons { get; private set; }

        /// <summary>
        /// Gets or sets the button repeat rate for this ButtonMap.
        /// </summary>
        public int RepeatRate { get; set; }

        /// <summary>
        /// Gets or sets the button hold threshold for this ButtonMap.
        /// </summary>
        public int HoldThreshold { get; set; }

        /// <summary>
        /// Gets a value indicating that the ButtonMap is up.
        /// </summary>
        public bool IsMapUp
        {
            get
            {
                bool status = false;

                foreach (Buttons button in MappedButtons)
                {
                    if (button.IsButtonUp())
                    {
                        status = true;
                    }
                }

                return status;
            }
        }

        /// <summary>
        /// Gets a value indicating that the ButtonMap is down.
        /// </summary>
        public bool IsMapDown
        {
            get
            {
                bool status = true;

                foreach (Buttons button in MappedButtons)
                {
                    if (button.IsButtonUp())
                    {
                        status = false;
                    }
                }

                return status;
            }
        }

        /// <summary>
        /// Gets a value indicating that the ButtonMap has been pressed.
        /// </summary>
        public bool WasMapPressed
        {
            get
            {
                bool status = false;

                foreach (Buttons button in MappedButtons)
                {
                    if (button.WasButtonUp())
                    {
                        status = true;
                    }
                }

                return (status && IsMapDown) ? true : false;
            }
        }

        /// <summary>
        /// Gets a value indicating that the ButtonMap has been released.
        /// </summary>
        public bool WasMapReleased
        {
            get
            {
                bool status = true;

                foreach (Buttons button in MappedButtons)
                {
                    if (button.WasButtonUp())
                    {
                        status = false;
                    }
                }

                return (status && IsMapUp) ? true : false;
            }
        }

        /// <summary>
        /// Gets a value indicating that the ButtonMap was held.
        /// </summary>
        public bool WasMapHeld
        {
            get
            {
                bool status = true;

                foreach (Buttons button in MappedButtons)
                {
                    if (button.WasButtonUp())
                    {
                        status = false;
                    }
                }

                return (status && IsMapDown) ? true : false;
            }
        }

        /// <summary>
        /// Initializes a new instance of the ButtonMap class.
        /// </summary>
        /// <param name="keys">The collection of buttons to map.</param>
        public MouseButtonMap(List<Buttons> buttons)
            : this(buttons, DefaultHoldThreshold, DefaultRepeatRate)
        {
        }

        /// <summary>
        /// Initializes a new instance of the ButtonMap class.
        /// </summary>
        /// <param name="keys">The collection of buttons to map.</param>
        /// <param name="holdThreshold">The timespan before the ButtonMap is considered held.</param>
        /// <param name="repeatRate">The timespan between repeating pressed events.</param>
        public MouseButtonMap(List<Buttons> buttons, int holdThreshold, int repeatRate)
        {
            HoldThreshold = holdThreshold;
            RepeatRate = repeatRate;
            MappedButtons = buttons;
            Register();
        }

        /// <summary>
        /// Determines if the specified button is managed by this ButtonMap.
        /// </summary>
        /// <param name="key">The button to search for.</param>
        /// <returns>True if this ButtonMap manages the specified button, otherwise false.</returns>
        public bool Contains(Buttons button)
        {
            foreach (Buttons b in MappedButtons)
            {
                if (b == button)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Determines if the specified buttons are managed by this ButtonMap.
        /// </summary>
        /// <param name="keys">The buttons to search for.</param>
        /// <returns>True if this ButtonMap manages the specified buttons, otherwise false.</returns>
        public bool Contains(List<Buttons> buttons)
        {
            if (buttons.Count != MappedButtons.Count)
            {
                return false;
            }

            foreach (Buttons b in buttons)
            {
                if (!Contains(b))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Determines if the current ButtonMap is equal to the specified ButtonMap.
        /// </summary>
        /// <param name="map">The ButtonMap to compare to.</param>
        /// <returns>True if equal, otherwise false.</returns>
        public override bool Equals(object obj)
        {
            if (System.Object.ReferenceEquals(this, obj))
            {
                return true;
            }

            if (obj != null && obj is MouseButtonMap)
            {
                MouseButtonMap buttonMap = obj as MouseButtonMap;

                if (MappedButtons.Count == buttonMap.MappedButtons.Count)
                {
                    foreach (Buttons b in MappedButtons)
                    {
                        if (!buttonMap.Contains(b))
                        {
                            return false;
                        }
                    }

                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Serves as a hash function for the ButtonMap type.
        /// </summary>
        /// <returns>The hash code for this ButtonMap.</returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Determines if the left ButtonMap is equal to the right ButtonMap.
        /// </summary>
        /// <param name="left">ButtonMap to the left of the operator.</param>
        /// <param name="right">ButtonMap to the right of the operator.</param>
        /// <returns>True if equal, otherwise false.</returns>
        public static bool operator ==(MouseButtonMap left, MouseButtonMap right)
        {
            if (System.Object.ReferenceEquals(left, right))
            {
                return true;
            }

            if (!System.Object.ReferenceEquals(left, null))
            {
                if (System.Object.ReferenceEquals(right, null))
                {
                    return false;
                }
                else
                {
                    return left.Equals(right);
                }
            }

            return false;
        }

        /// <summary>
        /// Determines if the left ButtonMap is not equal to the right ButtonMap.
        /// </summary>
        /// <param name="left">ButtonMap to the left of the operator.</param>
        /// <param name="right">ButtonMap to the right of the operator.</param>
        /// <returns>True if not equal, otherwise false.</returns>
        public static bool operator !=(MouseButtonMap left, MouseButtonMap right)
        {
            return (left == right) ? false : true;
        }

        private void NotifyEvent(EventHandler<InputMapEventArgs<MouseButtonMap>> eventHandler)
        {
            EventHandler<InputMapEventArgs<MouseButtonMap>> handler = eventHandler;

            if (handler != null)
            {
                handler(this, new InputMapEventArgs<MouseButtonMap>(this));
            }
        }

        #region IInputMap Members

        /// <summary>
        /// Update the IInputMap.
        /// </summary>
        /// <param name="gameTime">A snapshot of the elapsed game time.</param>
        public override void Update(GameTime gameTime)
        {
            if (WasMapPressed)
            {
                _millisecondsHeld = 0;
                NotifyEvent(Pressed);
            }
            else if (WasMapReleased)
            {
                NotifyEvent(Released);
            }
            else if (WasMapHeld)
            {
                _millisecondsHeld += gameTime.ElapsedRealTime.Milliseconds;

                if (HoldThreshold > 0 && _millisecondsHeld >= HoldThreshold)
                {
                    _lastRepeatTimeSpan += gameTime.ElapsedGameTime.Milliseconds;

                    if (RepeatRate == 0 || _lastRepeatTimeSpan >= RepeatRate)
                    {
                        NotifyEvent(Held);
                        _lastRepeatTimeSpan = 0;
                    }
                }
            }
        }

        #endregion
    }
}
